﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WBFSe.FormatApi;
using WBFSe.Attributes;
using WBFSe.IO;
using WBFSe.IO.Drives;
using System.IO;
using WBFSe.IO.Streams;

namespace WBFSe.Formats
{
    /// <summary>
    /// Wbfs partition creator
    /// </summary>
    class PWbfsCreator : WbfsCreator
    {
        // Variables

        private WbfsDrive drive;
        private int discSlot;
        private ushort[] sectors;
        private byte[] head;

        // Methods

        /// <summary>
        /// Initializes the creator with the game and container data
        /// </summary>
        /// <param name="directory">The destination directory</param>
        /// <param name="filename">The destination filename (no extension)</param>
        /// <param name="gameInfo">Built information about the disc</param>
        /// <param name="split">Container split information</param>
        /// <exception cref="System.Exception">On operation failure</exception>
        /// <remarks>Do not fail if the sector size is not right
        /// it will be verified and corrected automatically</remarks>
        public override void Initialize(String directory, String filename,
            IDiscExtendedInfo gameInfo, SplitInfo split)
        {
            this.version = 1;

            drive = WbfsDevices.GetDrive(directory) as WbfsDrive;

            this.path = directory;
            this.filenames = new List<string>();
            this.filenames.Add(String.Format("{0} {1}",
                gameInfo.Code, gameInfo.Name));
            
            // Forces the system to recalculate
            this.sectorSize = this.drive.WbfsSectorSize;
        }

        /// <summary>
        /// Sets a new usage table according to desired sector size
        /// </summary>
        /// <param name="usageTable">New sector usage table</param>
        /// <exception cref="System.Exception">On operation failure</exception>
        public override void ResetUsageTable(UsageTable usageTable)
        {
            this.sectorUsage = usageTable;
            this.estimatedSize = (long)this.sectorSize *
                usageTable.UsedSectors;
        }

        /// <summary>
        /// Generates a stream for writting on the container
        /// </summary>
        /// <returns>Stream</returns>
        /// <exception cref="System.Exception">On operation failure</exception>
        public override IWbfsStream CreateStream()
        {
            ushort[] sectors = null;

            if (!this.drive.AllocateSectors(this.sectorUsage.UsedSectors, out sectors))
                throw new IOException("The disc is full");

            if (!this.drive.AllocateDiscSlot(this.filenames[0], out discSlot))
            {
                this.drive.FreeSectors(sectors);
                this.sectors = null;

                if(this.discSlot == -1) throw new IOException("The disc is full");
                else throw new IOException("The file already exists");
            }

            int j = 0;
            this.sectors = new ushort[this.sectorUsage.Count];
            for (int i = 0; i < this.sectorUsage.Count; i++)
            {
                if (!this.sectorUsage[i]) this.sectors[i] = 0;
                else this.sectors[i] = sectors[j++];
            }

            return new WbfsDriveDiscStream(this.drive,
                this.sectors, this.drive.DiscLength);
        }

        /// <summary>
        /// Finishes the container
        /// </summary>
        /// <param name="successful">The operation was successful</param>
        /// <exception cref="System.Exception">On operation failure</exception>
        public override void End(bool successful)
        {
            if (!successful)
            {
                this.drive.FreeSectors(this.sectors);
                this.drive.FreeDiscSlot(this.discSlot);
            }
            else
            {
                try
                {
                    IWbfsStream stream = new WbfsDriveDiscStream(this.drive,
                        this.sectors, this.drive.DiscLength);

                    UnmanagedArray buffer = new UnmanagedArray(this.drive.HdSectorSize);
                    stream.Read(buffer.DataPtr, 0, 0, this.drive.HdSectorSize);

                    this.head = new Byte[WbfsConstantsV1.WbfsDiscHCopySz];
                    buffer.CopyTo(this.head, 0, WbfsConstantsV1.WbfsDiscHCopySz);
                    buffer.Dispose();

                    if (!this.drive.SetDiscDesc(this.filenames[0], this.head, this.sectors))
                        throw new FileNotFoundException("File not found", this.filenames[0]);
                }
                catch
                {
                    this.drive.FreeSectors(this.sectors);
                    this.drive.FreeDiscSlot(this.discSlot);
                    throw;
                }

                this.drive.Synchronize();
            }
        }

        /// <summary>
        /// Disposes the container
        /// </summary>
        public override void Dispose()
        {
            // Do nothing...
        }
    }
}
